Core Animation
概要
Core Animation は、iOS および macOS で利用できるアニメーションのためのインフラストラクチャ。いくつかのパラメータを指定するだけでアニメーションを簡単に描画できる API を提供している。 UIKit におけるスムーズなスクロールや View 遷移も、内部的には Core Animation に依存している。 code:text
+------------------------+
| UIKit/AppKit |
+------------------------+
+------------------------+
| Core Animation |
+------------------------+
+-------++---------------+
| Metal || Core Graphics |
+-------++---------------+
+------------------------+
| Hardware |
+------------------------+
Layer は、画面上に描画されるコンテンツとその視覚情報を管理する、Core Animation の中心となる概念。View と同様に矩形のオブジェクトであり、背景色やサイズ等の視覚情報を保持し、階層構造を構築する。一方で、View と異なり自身は描画されない。また、ユーザインタラクションやイベントのハンドリングも行わない。 UIKit/AppKit は、各々サポートしているプラットフォームの UI パラダイムが大きく異なる。前者はマルチタッチをサポートし、後者はマウス/キーボードによる操作が主になっている。そのため、イベントハンドリングや Responder chain 周りの仕組みも大きく異なり、UIKit/AppKit はこの周辺を担当している。一方で、Layer はプラットフォーム間で共通して利用できる描画, レイアウト, アニメーション周りを担当していて、両者の間で共通して利用できるようになっている。 Layer は自身を描画するといったことはなく、描画対象のコンテンツとその視覚情報の管理が主な責務になる。情報管理が主な責務であることから、モデルオブジェクト と呼ばれることもある。Layer は、描画対象のコンテンツを Bitmap、すなわちピクセル画像として保持している。CALayer のプロパティでいうと contents が該当する。この保持されている Bitmap のことは、View 等の描画対象のコンテンツの Bitmap 表現を裏で Layer 内部にキャッシュしてることから backing store とも呼ばれる。 Layer を利用した場合のアニメーションは、以下のような手順になる。 1. Layer オブジェクトの backing store に View の Bitmap がキャッシュされる
2. アニメーションさせたい View の視覚情報について、Layer オブジェクトの視覚情報を編集する
3. Layer オブジェクトにアニメーションを追加する
5. グラフィクスハードウェアから変換後の Bitmap を受け取る
6. 変換後の View を描画する
View の描画の場合は、最終的には draw(_:) メソッドが呼び出されることになるけれど、これは新しい視覚情報を用いてコンテンツを描画し直すことになる。この操作は CPU のメインスレッドを占有するため、コストが高い。一方で、Layer 及び Core Animation を利用した場合、backing store 内の Bitmap のキャッシュを利用してグラフィクスハードウェアで直接計算し直すため、より効果的にアニメーションを描画できる。 Model/Presenter
Core Animation は、CALayer のプロパティへの値の代入時に、デフォルトでアニメーションを実行する。したがって、CALayer の各プロパティは、基本的には値が設定されてもそれがすぐに画面に反映されない。もっと正確にいうと、値の設定自体は通常のプロパティと同様に即座に行われる (そのため、設定直後に該当プロパティを参照すると、設定した値になっていることがわかる) が、それが画面に即座には反映されない。素直に生成した CALayer オブジェクトのプロパティは、最終的に到達すべき目標値 を保持している、ということになる。 一方で、現在描画中の値についても別途保持されている。Core Animationはこの状態管理のために、計3つの別々の Layer オブジェクトを扱っており、各オブジェクトは各々異なる責務を持つ。 Model Layer
アニメーションの最終到達値を管理する
Presentation Layer
実行中のアニメーション状態を反映した値を管理する
Render Layer
実際のアニメーションを扱う
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/Art/sublayer_hierarchies_2x.png
CALayer オブジェクトには、Model 及び Presenter にアクセスするためのプロパティとして model() 及び presentation() メソッドを持つ。model() メソッドは、Presentation Tree 内の Layer オブジェクトに対して呼び出すと対応する Model Layer オブジェクトを返し、そうではない通常の Layer オブジェクトの場合は自身を返す。 暗黙的/明示的アニメーション
CALayer は、アニメーション可能なプロパティなプロパティは、全てデフォルトでアニメーションする。この時のアニメーション設定は CATransaction のものが利用される。例えば、アニメーションにかかる秒数は CATransaction.animationDuration() で確認でき、デフォルトだと 0.25 秒になる。 code:swift
class ViewController: UIViewController {
@IBOutlet var myView: UIView!
private var subLayer: CALayer!
@IBOutlet var subLayerPresentationBackgroundColor: UILabel!
@IBOutlet var subLayerModelBackgroundColor: UILabel!
private var isToggledSubLayerColor: Bool = false
var nextSublayerColor: UIColor {
return self.isToggledSubLayerColor ? .green : .gray
}
var currentSublayerColor: UIColor {
return self.isToggledSubLayerColor ? .gray : .green
}
/// CoreAnimationのAPIで秒数を指定してアニメーションする
/// 指定した3秒でアニメーションする
@IBAction func changeSublayerColoWithCoreAnimationAPI(_ sender: Any) {
let animation = CABasicAnimation(keyPath: "backgroundColor")
animation.fromValue = self.currentSublayerColor.cgColor
animation.toValue = self.nextSublayerColor.cgColor
animation.duration = 3
self.subLayer.backgroundColor = self.nextSublayerColor.cgColor
self.subLayer.add(animation, forKey: "backgroundColor")
self.isToggledSubLayerColor = !self.isToggledSubLayerColor
}
/// UIViewのAPIで秒数を指定してアニメーションする
/// が、これは動作しない
@IBAction func changeSublayerColorWithUIKitAPI(_ sender: Any) {
UIView.animate(withDuration: 3, animations: {
self.subLayer.backgroundColor = self.nextSublayerColor.cgColor
})
self.isToggledSubLayerColor = !self.isToggledSubLayerColor
}
/// シンプルな値の代入
/// デフォルトのアニメーションが行われる(0.25秒)
@IBAction func changeSublayerColorWithSimpleAssignValue(_ sender: Any) {
self.subLayer.backgroundColor = self.nextSublayerColor.cgColor
self.isToggledSubLayerColor = !self.isToggledSubLayerColor
}
override func viewDidLoad() {
super.viewDidLoad()
self.subLayer = CALayer()
self.subLayer.frame = .init(x: 50, y: 50, width: 100, height: 100)
self.subLayer.backgroundColor = UIColor.green.cgColor
self.myView.layer.addSublayer(self.subLayer)
}
}
https://gyazo.com/eacf4ddc7a84a3a52bf25f1bc1801435
code:swift
class ViewController: UIViewController {
@IBOutlet var myView: UIView!
private var subLayer: CALayer!
@IBOutlet var presentationBackgroundColor: UILabel!
@IBOutlet var modelBackgroundColor: UILabel!
private var isToggledColor: Bool = false
var nextColor: UIColor {
return self.isToggledColor ? .systemPink : .blue
}
var currentColor: UIColor {
return self.isToggledColor ? .blue : .systemPink
}
/// 省略
/// CoreAnimationのAPIで秒数を指定してアニメーションする
/// 正常に動作する
@IBAction func changeColorWithCoreAnimationAPI(_ sender: Any) {
let animation = CABasicAnimation(keyPath: "backgroundColor")
animation.fromValue = self.currentColor.cgColor
animation.toValue = self.nextColor.cgColor
animation.duration = 3
self.myView.layer.backgroundColor = self.nextColor.cgColor
self.myView.layer.add(animation, forKey: "backgroundColor")
self.isToggledColor = !self.isToggledColor
}
/// UIKitのAPIで秒数を指定してアニメーションする
/// 正常に動作する
@IBAction func changeColorWithUIKitAPI(_ sender: Any) {
UIView.animate(withDuration: 3, animations: {
self.myView.layer.backgroundColor = self.nextColor.cgColor
})
self.isToggledColor = !self.isToggledColor
}
/// シンプルな値の代入
/// アニメーションしない
@IBAction func changeColorWithAssignValue(_ sender: Any) {
self.myView.layer.backgroundColor = self.nextColor.cgColor
self.isToggledColor = !self.isToggledColor
}
/// 省略
override func viewDidLoad() {
super.viewDidLoad()
self.subLayer = CALayer()
self.subLayer.frame = .init(x: 50, y: 50, width: 100, height: 100)
self.subLayer.backgroundColor = UIColor.green.cgColor
self.myView.layer.addSublayer(self.subLayer)
}
}
https://gyazo.com/b14108c687b027eed047f9aa8c06e891
2. delegate を保持していないか、保持していても該当メソッドを実装していなかった場合、actions を確認する 3. それでも見つかれなければ、style プロパティをチェックする code:swift
class ViewController: UIViewController {
@IBOutlet var myView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Optional(<null>)
print(self.myView.action(for: self.myView.layer, forKey: "backgroundColor")
UIView.animation(withDuration: 3, animations: {
// Optional(<CABasicAnimation:0x6000029d30a0;
// delegate = <UIViewAnimationState: 0x7fcc5a605090>;
// fillMode = both;
// timingFunction = easeInEaseOut;
// duration = 3;
// fromValue = <CGColor 0x600000dc8660> [
// <CGColorSpace 0x600000dc00c0> (kCGColorSpaceICCBased;
// kCGColorSpaceModelRGB;
// sRGB IEC61966-2.1;
// extended range)
// ] ( 1 0.176471 0.333333 1 );
// keyPath = backgroundColor>)
print(self.myView.action(for: self.myView.layer, forKey: "backgroundColor")
})
}
}
アニメーションに関連するクラスは以下がある。
code:text
CAAnimation
┣ CAAnimationGroup
┣ CATransition
└ CAPropertyAnimation
┣ CABasicAnimation
┃ └ CASpringAnimation
└ CAKeyframeAnimation
CAAnimation は、CAMediaTiming 及び CAAction プロトコルに適合しており、アニメーション速度を調整するためのタイミング関数や、delegate への通知等の最低限の実装を持つ。
CAAnimationGroupは、その名の通り複数の CAAnimation をグループ化できるクラス。複数のアニメーションを取りまとめて、並列/直列に実行させることができる。
CAPropertyAnimation は、特定のプロパティについて、ある値からある値へのアニメーションを提供するための抽象クラス。
CATransition は、プロパティアニメーションでは実現できないような、複雑なアニメーションの実現に利用できる。プロパティアニメーションはアニメーション可能なプロパティについてのみしかアニメーションできないため、それ以外のテキストや画像等のアニメーションや、Layer の追加/削除を伴うアニメーション等には利用できない。CATransition は、レイヤー全体を遷移させるようなアニメーションに向いている。
アニメーションのキャンセル
TODO: サンプルコードを書いてみる。
UIKit におけるアニメーションについて
If a layer belongs to a layer-backed view, the recommended way to create animations is to use the view-based animation interfaces provided by UIKit or AppKit. There are ways to animate the layer directly using Core Animation interfaces but how you create those animations depends on the target platform.
TODO: ここを読む
参考